Repository navigation
Conversation
test/modules/http3 runs requests over HTTP/3 with curl --http3-only against a QUIC listener on the https port number. It covers static files, HEAD, 404, a 1 MB response, several requests on one connection, virtual host selection by Host, POST content to CGI up to 1 MB and the CGI environment. The package is skipped when mod_http3, mod_ssl or curl's HTTP3 feature is missing. Generated-by: Claude Code (Claude Opus 5.5)
stream_worker() built the request_rec and went straight to
ap_process_request(), with only ap_update_vhost_from_headers() in
between. It skipped the two steps that HTTP/1.1 (ap_read_request()) and
HTTP/2 (h2_create_request_rec()) run on every request:
* ap_check_request_header(): vhost selection with StrictHostCheck, the
missing Host check and Expect handling.
* ap_post_read_request(): the post_read_request hooks of mod_setenvif,
mod_remoteip, mod_headers ("early"), mod_proxy (forward proxy
detection), mod_md, mod_unique_id and others, plus the core checks on
the complete request. mod_http3's own h3_hook_post_read_request never
ran for its own requests either.
So configuration behaved differently over HTTP/3, including access
control: with "SetEnvIf User-Agent ^evilbot bad_bot" and
"Require not env bad_bot", a request was denied with 403 over HTTP/1.1
but served with 200 over HTTP/3. Both now give 403.
Add test_003_access.py to the mod_http3 tests for this.
Generated-by: Claude Code (Claude Opus 5.5)
configure checked for nghttp3_conn_server_new(), which nghttp3 1.x only provides as a macro around nghttp3_conn_server_new_versioned(), so the link test failed and --enable-http3 always reported "nghttp3 library is unusable". Check for the versioned function instead. h3_io.c calls nghttp3_conn_is_drained2(), which was added in nghttp3 1.16.0 while configure accepts nghttp3 >= 1.0.0. Check for it and fall back to nghttp3_conn_is_drained(), which has the same semantics, so the module builds against e.g. the nghttp3 1.8.0 in Debian 13. Generated-by: Claude Code (Claude Opus 5.5)
OpenSSL before 3.5.7 frees a stream's send buffer once the peer acknowledges the FIN sent by SSL_stream_conclude(), and SSL_get_stream_write_state() then dereferences it (fixed upstream by "Make SSL_get_stream_write_state() safe for concluded streams"). Children crashed this way in drain_one_stream() on Ubuntu 26.04, which ships OpenSSL 3.5.5. Mark a stream through its SSL app data when it is concluded, and treat its sending half as finished without asking OpenSSL. Generated-by: Claude Code (Claude Opus 5.5)
The threads of mod_http3 allocated from the child pool, which is not thread-safe, and kept running while it was destroyed: - The port acquirer thread, in every child that does not own the UDP port, created a socket in the child pool every 200 ms, racing with the main thread and never freeing it. - Under prefork the child pool's allocator has no mutex and is shared with the request pools, which the event thread raced with when it created connection pools. - prefork does not run the child_stopping hook when a child exits from a signal handler, so neither thread was stopped before the child pool was destroyed and exit() ran OpenSSL's cleanup under them. These crashed children with SIGSEGV on stop, mostly under prefork. Give mod_http3 a pool of its own with a locked allocator, give each port acquisition attempt a scratch pool that is cleared on retry, and stop the threads from a pre-cleanup of the child pool as well. Generated-by: Claude Code (Claude Opus 5.5)
When a child stops, connections still open after the GOAWAY grace period are shut down with SSL_SHUTDOWN_FLAG_RAPID. That flag skips the terminating period, but OpenSSL still waits for all stream data to be acknowledged first. The GOAWAY just sent to a client that has already gone away never is, so the event thread waited for the idle timeout and the parent killed the child with SIGKILL (AH00046). Add SSL_SHUTDOWN_FLAG_NO_STREAM_FLUSH to the rapid shutdown. Generated-by: Claude Code (Claude Opus 5.5)
On a stop, every connection waited the full GOAWAY grace period of H3_GOAWAY_GRACE_SECS, even when no request was in flight: nghttp3_conn_is_drained() only reports a drained connection after nghttp3_conn_shutdown(), and that was only called once the connection was drained or the grace period was over. A child with an idle HTTP/3 connection therefore outlived the parent's first wait and was sent SIGTERM (AH00045). On an ungraceful stop, call nghttp3_conn_shutdown() right after the shutdown notice, so that new streams are refused and is_drained reports only the streams in flight. Idle connections close at once; requests in flight still get the grace period. Generated-by: Claude Code (Claude Opus 5.5)
Add a "mod_http3" job on Ubuntu 26.04, which has OpenSSL 3.5 and libnghttp3, that builds with --enable-http3 and runs modules/http3. The runner's curl has no HTTP/3, so TEST_CURL_H3 builds the given curl release with OpenSSL QUIC and libnghttp3 into the cached ~/root, and the run points pyhttpd at it through $CURL, failing early when that curl lacks HTTP3. The job sets PYHTTPD_STRICT_OPTIONAL, so a missing mod_http3 or HTTP/3 curl fails it instead of skipping the suite. Generated-by: Claude Code (Claude Opus 5.5)
mbeijen
marked this pull request as draft
November 4, 2026 17:39
Review feedback: define H3_NGHTTP3_CONN_IS_DRAINED() for both nghttp3 versions instead of redefining the lowercase nghttp3 function name. * modules/http3/h3_io.c: Add H3_NGHTTP3_CONN_IS_DRAINED() and use it. Generated-by: Claude Code (Claude Opus 5.5)
Review feedback: the HAVE_NGHTTP3_CONN_IS_DRAINED2 check existed only in the autoconf build, so CMake builds always fell back to the old function. Test NGHTTP3_VERSION_NUM instead, which every build gets from <nghttp3/version.h>. Also keep the declaration of "drained" at the top of its block, as C89 requires. * modules/http3/config2.m4: Drop the nghttp3_conn_is_drained2 check. * modules/http3/h3_io.c (H3_NGHTTP3_CONN_IS_DRAINED): Use nghttp3_conn_is_drained2() from nghttp3 1.16.0 on. (service_session_pass): Declare "drained" at the top of the block. Generated-by: Claude Code (Claude Opus 5.5)
Review feedback: keep the body of stop_child_threads() in h3_c1_child_stopping(), which the child pool cleanup now calls, so no forward declaration is needed. Move the pool creation to h3_threads.c so other code can reuse it. * modules/http3/h3_server.c (h3_c1_child_stopping): Fold in stop_child_threads(). * modules/http3/h3_threads.c, modules/http3/include/h3_threads.h (h3_thread_safe_pool_create): New, from create_h3_pool() in h3_server.c. Generated-by: Claude Code (Claude Opus 5.5)
Review feedback: the mutex on the allocator of the HTTP/3 pool covers subpools, not allocations from the pool itself. h3_io_listen_start() registered its pool cleanup after starting the event thread, which allocates from the same pool, so the two could allocate at once when the port acquirer thread started listening. Register it first. Also destroy the pool when its mutex cannot be created, log when the port acquirer cannot create its pool, drop an empty APLOGNO() that no other message in the file has, and keep the declarations in h3_child_init() at the top of their blocks, as C89 requires. * modules/http3/h3_io.c (h3_io_listen_start): Register the cleanup before starting the event thread. * modules/http3/h3_server.c (port_acquire_thread_fn): Log the pool creation failure. (h3_child_init): Declare variables first; drop the APLOGNO(). * modules/http3/h3_threads.c (h3_thread_safe_pool_create): Destroy the pool if its mutex cannot be created. * modules/http3/include/h3_threads.h: Say what the mutex does not cover. Generated-by: Claude Code (Claude Opus 5.5)
# Conflicts: # modules/http3/h3_threads.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This adds a
mod_http3job on Ubuntu 26.04 (OpenSSL 3.5, packaged libnghttp3) that builds with--enable-http3and runstest/modules/http3. The runner's curl has no HTTP/3, soTEST_CURL_H3builds a curl release with OpenSSL QUIC and libnghttp3 into the cached~/root.PYHTTPD_STRICT_OPTIONALmakes a missing mod_http3 or HTTP/3 curl fail the job instead of skipping the suite.The job only passes with #938, #939, #940, #941, #942, #943 and #944 in, so this branch carries copies of them. I'll rebase it once those are merged.
SECURITY.mdand https://www.apache.org/security/#reporting-a-vulnerability).APLOGNO()tag; numbers are assigned by a committer at merge time (seedocs/log-message-tags/README).changes-entries/*.txtfile is included, following the template inREADME.CHANGES(not needed otherwise).test/modules/xxxdirectory matching themodules/xxxmodule source, ortest/modules/corefor core server changes. On Unix, verify these pass locally with e.g.make check-pytest PYTEST_DIRS=test/modules/xxx.